Spring MVC并发模型
Spring Controller的并发模型
单例Bean与多线程
- Controller默认是单例:Spring容器中每个Controller只有一个实例
- 方法是多线程并发执行:Servlet容器(如Tomcat)为每个请求分配独立的线程
- 成员变量是线程共享的:需要注意线程安全问题
- 局部变量是线程隔离的:每个线程有自己的栈空间
代码示例
java
@RestController
public class DemoController {
// 线程共享的成员变量 - 存在线程安全问题
private int counter = 0;
// 线程隔离的局部变量
@GetMapping("/test")
public String test() {
int localVar = 0; // 每个线程都有自己的副本
localVar++;
// 这里的counter需要同步
synchronized(this) {
counter++;
}
return "Count: " + counter;
}
}线程执行流程
graph TD
A[客户端请求1] --> B[Tomcat线程池]
A2[客户端请求2] --> B
A3[客户端请求3] --> B
B --> C[分配线程1]
B --> D[分配线程2]
B --> E[分配线程3]
C --> F[执行Controller方法]
D --> F
E --> F
F --> G[返回响应]Servlet容器的线程模型
Tomcat线程池配置
yaml
# application.yml
server:
tomcat:
threads:
max: 200 # 最大线程数
min-spare: 10 # 最小空闲线程
max-connections: 10000请求处理流程
请求到达 → Tomcat Acceptor接收 → 放入连接队列
→ 从线程池获取工作线程 → 执行Spring Controller方法
→ 返回响应 → 线程归还线程池并发场景下的锁使用策略
需要加锁的场景
| 场景 | 是否需要锁 | 原因 |
|---|---|---|
| 修改共享成员变量 | 是 | 多线程同时修改导致数据不一致 |
| 防止重复远程调用 | 是 | 避免缓存击穿,减少资源浪费 |
| 数据库更新操作 | 是 | 数据库事务隔离,但应用层也需要考虑 |
| 幂等性控制 | 是 | 防止重复提交 |
锁粒度的选择
// 1. 方法级锁 - 最简单,但并发度低
public synchronized Result method() { }
// 2. 对象级锁 - 中等粒度
private final Object lock = new Object();
synchronized(lock) { }
// 3. 分段锁 - 提高并发
private final Object[] segmentLocks = new Object[16];
synchronized(segmentLocks[id.hashCode() % 16]) { }
// 4. 细粒度锁 - 最高并发
private final ConcurrentHashMap<Long, Object> locks = new ConcurrentHashMap<>();
synchronized(locks.computeIfAbsent(id, k -> new Object())) { }分段锁初始化:
javaprivate final Object[] segmentLocks = new Object[16]; // 必须先初始化数组中的每一个锁对象! { for (int i = 0; i < segmentLocks.length; i++) { segmentLocks[i] = new Object(); } } // 通过hash和位运算,获取到锁 synchronized(segmentLocks[(id.hashCode() & Integer.MAX_VALUE) & 15]) { }
实际应用中的并发控制
缓存击穿防护模式
java
@Component
public class ConcurrentControlService {
private final ConcurrentHashMap<String, Future<?>> locks = new ConcurrentHashMap<>();
public <T> T execute(String key, Supplier<T> supplier) {
// 双重检查避免重复执行
T result = getFromCache(key);
if (result != null) {
return result;
}
synchronized (getLock(key)) {
result = getFromCache(key);
if (result != null) {
return result;
}
// 执行实际逻辑
result = supplier.get();
saveToCache(key, result);
return result;
}
}
private Object getLock(String key) {
return locks.computeIfAbsent(key, k -> new Object());
}
}Spring Cache的并发控制
java
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setCacheNames(Arrays.asList("users", "orders"));
return cacheManager;
}
}
@Service
public class OrderService {
// sync=true 确保只有一个线程执行方法体
@Cacheable(value = "orders", key = "#id", sync = true)
public Order getOrder(Long id) {
// 只会有一个线程执行这里
return fetchFromDatabase(id);
}
}避免并发时的重复提交
java
@PostMapping("/submit")
@Transactional
public Result submit(@RequestBody Request request) {
// 使用分布式锁或数据库唯一约束
String lockKey = "submit:" + request.getId();
if (!redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) {
throw new BusinessException("请勿重复提交");
}
try {
// 业务逻辑
} finally {
redisLock.unlock(lockKey);
}
}浏览器并发请求限制
HTTP/1.1的并发限制
| 浏览器 | HTTP/1.1 同域名并发数 | HTTP/2 并发数 |
|---|---|---|
| Chrome | 6 | 无限制(多路复用) |
| Firefox | 6 | 无限制 |
| Safari | 6 | 无限制 |
| Edge | 6 | 无限制 |
浏览器队列机制
// 超过并发数的请求会被放入队列
// 假设并发限制为6
1-6个请求 → 立即发送
第7个请求 → 等待,直到有请求完成释放连接
第8个请求 → 继续等待...突破限制的方法
<!-- 1. 使用不同子域名 -->
<img src="https://static1.example.com/a.jpg">
<img src="https://static2.example.com/b.jpg">
<!-- 2. 使用HTTP/2 -->
<!-- HTTP/2支持多路复用,无并发限制 -->
<!-- 3. 使用CDN分散域名 -->
